package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

// This program is generates code for wrapping interfaces which implement
// optional interfaces.  For some context on the problem this solves, read:
// https://blog.merovius.de/2017/07/30/the-trouble-with-optional-interfaces.html

// This problem takes one of the json files in this directory as input:
// eg.  go run main.go transaction_response_writer.json

func main() {
	if len(os.Args) < 2 {
		fmt.Println("provide input file")
		os.Exit(1)
	}
	filename := os.Args[1]
	inputBytes, err := ioutil.ReadFile(filename)
	if nil != err {
		fmt.Println(fmt.Errorf("unable to read %v: %v", filename, err))
		os.Exit(1)
	}

	var input struct {
		// variableName must implement all of the required interfaces
		// and all of the optional interfaces.  It will be used to
		// populate the fields of anonymous structs which have
		// interfaces embedded.
		VariableName string `json:"variable_name"`
		// variableName is the variable that will be tested against the
		// optional interfaces.  It is the "thing being wrapped" whose
		// behavior we seek to emulate.
		TestVariableName   string   `json:"test_variable_name"`
		RequiresInterfaces []string `json:"required_interfaces"`
		OptionalInterfaces []string `json:"optional_interfaces"`
	}

	err = json.Unmarshal(inputBytes, &input)
	if nil != err {
		fmt.Println(fmt.Errorf("unable to unmarshal input: %v", err))
		os.Exit(1)
	}

	bitflagVariables := make([]string, len(input.OptionalInterfaces))
	for idx := range input.OptionalInterfaces {
		bitflagVariables[idx] = fmt.Sprintf("i%d", idx)
	}

	fmt.Println("// GENERATED CODE DO NOT MODIFY")
	fmt.Println("// This code generated by internal/tools/interface-wrapping")
	fmt.Println("var (")
	for idx := range input.OptionalInterfaces {
		fmt.Println(fmt.Sprintf("%s int32 = 1 << %d", bitflagVariables[idx], idx))
	}
	fmt.Println(")")
	// interfaceSet is a bitset whose value represents the optional
	// interfaces that $input.TestVariableName implements.
	fmt.Println("var interfaceSet int32")
	for idx, inter := range input.OptionalInterfaces {
		fmt.Println(fmt.Sprintf("if _, ok := %s.(%s); ok {", input.TestVariableName, inter))
		fmt.Println(fmt.Sprintf("interfaceSet |= %s", bitflagVariables[idx]))
		fmt.Println("}")
	}
	permutations := make([][]int, 1<<uint32(len(input.OptionalInterfaces)))
	for permutationNumber := range permutations {
		for idx := range input.OptionalInterfaces {
			if 0 != (permutationNumber & (1 << uint32(idx))) {
				permutations[permutationNumber] = append(permutations[permutationNumber], idx)
			}
		}
	}
	fmt.Println("switch interfaceSet {")
	for _, permutation := range permutations {
		var cs string
		for i, elem := range permutation {
			if i > 0 {
				cs += " | "
			}
			cs += bitflagVariables[elem]
		}
		if cs == "" {
			fmt.Println("default: // No optional interfaces implemented")
		} else {
			fmt.Println(fmt.Sprintf("case %s:", cs))
		}
		fmt.Println("return struct {")
		for _, required := range input.RequiresInterfaces {
			fmt.Println(required)
		}
		for _, elem := range permutation {
			fmt.Println(input.OptionalInterfaces[elem])
		}
		totalImplements := len(input.RequiresInterfaces) + len(permutation)
		var varList string
		for i := 0; i < totalImplements; i++ {
			if i > 0 {
				varList += ", "
			}
			varList += input.VariableName
		}
		fmt.Println("} { " + varList + " }")
	}
	fmt.Println("}")
}
